Skip to content

Comments

Service tests#134

Open
thestumonkey wants to merge 11 commits intodevfrom
service_tests
Open

Service tests#134
thestumonkey wants to merge 11 commits intodevfrom
service_tests

Conversation

@thestumonkey
Copy link
Member

No description provided.

thestumonkey and others added 11 commits January 24, 2026 14:12
* Added k8s deploy and wip with service instances

* wip with instances

* Pre-rename checkpoint: Port conflict work + architecture docs

- Added port conflict detection to deployment flow
- Created architecture documentation (ARCHITECTURE_OVERVIEW.md, UNIFIED_CONFIG_ARCHITECTURE.md)
- Added rename script for Instance → ServiceConfig refactoring
- K8s deployment files and configuration updates
- Ready for automated renaming via scripts/rename_to_service_config.py

* refectored instances and k8s deployds

* Fix IPv6 DNS resolution for uv/Rust applications in Kubernetes

Root cause: Kubernetes ndots:5 + search domains breaks Rust DNS resolver
in uv 0.6.10, causing "dns error: failed to lookup address information"

Solution: Set dnsConfig ndots:1 while keeping ClusterFirst policy
- Allows K8s service discovery (redis.default.svc.cluster.local)
- Fixes uv package downloads (pypi.org resolves correctly)
- Uses spec.dns_policy to allow override per-service

Changes:
- kubernetes_manager.py: Added dnsConfig with ndots:1
- Comprehensive documentation of IPv6 dual-stack investigation
- Test scripts for IPv6 connectivity verification

Verified with Chronicle backend image (ghcr.io/ushadow-io/chronicle/backend:nodeps1):
✅ uv pip install setuptools httpx - works
✅ Python DNS resolution - works
✅ K8s service discovery - works

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Configuration updates and service refactoring

- Updated Chronicle compose configuration
- Refactored config structure (instances.yaml, wiring.yaml)
- Updated feature flags and defaults
- Frontend App.tsx updates

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* daily commit

* default services working

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…d: (#111)

`★ Insight ─────────────────────────────────────`
**Changes made to reduce vertical space:**
1. **Header**: Moved the checkmark icon inline with the title (was 64px centered, now 40px beside text) - saves ~80px
2. **Access URL box**: Reduced padding from `p-6` to `p-4`, removed the extra descriptive text above/below the URL - saves ~40px
3. **Configuration checklist**: Changed from vertical list (5+ items × ~32px each) to a compact horizontal flexwrap layout - saves ~100px
4. **Routes section**: Made it a collapsible `<details>` element with a `max-h-32` constraint when expanded - variable-height content now hidden by default
`─────────────────────────────────────────────────`

The complete step now has a much more compact layout where:
- The success header is inline rather than stacked
- Configuration status is shown as compact inline badges instead of a vertical list
- The variable-height routes section is collapsed by default (users can click "View configured routes" to expand)
- The `WhatsNext` component and navigation buttons should now be visible above the fold
* All five issues have been fixed. Let me provide a summary:

`★ Insight ─────────────────────────────────────`
The import dialog had several UX issues stemming from incomplete state management and rigid options. The fixes add proper refresh timing, editable fields where needed, and match the existing network naming convention used throughout the codebase.
`─────────────────────────────────────────────────`

## Summary of Fixes

### 1. Post-import refresh (line 514)
**Problem:** Had to manually refresh after import.
**Fix:** Added `onServiceImported()` call immediately after successful import, not just when clicking "Done".

### 2. Variable name editing (lines 1072-1106)
**Problem:** Couldn't enter variable names when adding new env vars.
**Fix:** Name input is now editable for:
- All Docker Hub imports
- Manually added variables (those with empty names)
- Also added delete button for manually added vars

### 3. Adding custom capabilities (lines 862-904)
**Problem:** Could only select from preset list.
**Fix:** Added:
- `transcription` to the preset options
- Custom capability text input with "Add" button
- Displays all selected capabilities as removable chips

### 4. Required capabilities selection (lines 907-956)
**Problem:** No way to specify which capabilities a service depends on.
**Fix:** Added a new "Capabilities Required" section with:
- Same capability options as "Provided"
- Amber styling to distinguish from green "Provides"
- Displays selected requirements as removable chips
- Wired through to API and backend (`requires` field in x-ushadow metadata)

### 5. Docker network naming (line 335)
**Problem:** Generated `${COMPOSE_PROJECT_NAME:-ushadow}_infra-network` but existing compose files use just `infra-network`.
**Fix:** Changed to `name: 'infra-network'` to match existing pattern in `docker-compose.infra.yml` and other compose files.

---

**Regarding your question about Docker networks:** The current pattern uses a single `infra-network` that all services join. This is simple and works well when services need to communicate. You could add a separate `ushadow` network for the main app stack if you want isolation, but for most use cases a single shared network is sufficient. The key is consistency - all your compose files now use the same `infra-network` name.

* ## Summary

I've created two reusable components and refactored the import modal:

### New Components

**1. `CapabilitySelector.tsx`** - Reusable capability selection
- Exports `CAPABILITY_OPTIONS` constant for consistency across the app
- `mode` prop: `'provides'` (blue styling) or `'requires'` (amber styling)
- Built-in custom capability input with validation
- Removable chips for selected items
- Proper `data-testid` attributes

**2. `EnvVarListEditor.tsx`** - Reusable env var list management
- Add/remove variables
- Name editing (configurable via `allowNameEdit`)
- Source selection: literal value, default, or from settings
- Bulk paste from `.env` format
- Auto-detect secrets by name pattern
- Show/hide toggle for secret values

### Refactored Modal

The `ImportFromGitHubModal` now:
- Uses `CapabilitySelector` for both "provides" and "requires" sections
- Uses `EnvVarListEditor` for environment variables
- Removed ~200 lines of duplicated inline code
- Cleaner separation of concerns

---

### Regarding Your Architecture Question

**You asked about flows as components vs contexts:**

For your current use case, I'd recommend this evolution path:

```
Current (what we have):
ImportFromGitHubModal
├── renderUrlStep()      ─ inline render functions
├── renderComposeStep()
├── renderServiceStep()
├── renderConfigStep()   ─ uses CapabilitySelector, EnvVarListEditor
└── renderCompleteStep()

Future (if it grows more complex):
ImportServiceModal (container)
├── StepIndicator (shared)
├── UrlStep (component)
├── GitHubComposeStep (GitHub only)
├── GitHubServiceStep (GitHub only)
├── ServiceConfigStep (shared) ─ uses CapabilitySelector, EnvVarListEditor
└── CompleteStep (shared)
```

The current inline render functions work fine for now. Extract to separate components when:
1. The modal grows beyond ~500-600 lines
2. You want to test steps in isolation
3. Steps need their own state management

---

**Your decision point on suggestions:** Do you want me to add settings suggestion support to `EnvVarListEditor`? This would let users auto-map env vars to existing settings during import (like the existing `EnvVarEditor` does for service configuration).

* I've completed adding settings suggestion support to the `EnvVarListEditor` component. Here's what was added:

`★ Insight ─────────────────────────────────────`
**Component Architecture Decisions:**
1. **Fuzzy matching via scoring** - The `findMatchingSuggestions` function uses a scoring algorithm (exact match = 100, partial = 50, shared words = 25×n) to rank suggestions by relevance to the env var name
2. **Optgroup for visual hierarchy** - The dropdown uses `<optgroup>` labels to separate "Suggested matches" from "All settings", making it easier to find the right mapping
3. **Graceful degradation** - When no suggestions are available, the component falls back to a text input for manual entry
`─────────────────────────────────────────────────`

**Summary of changes to `EnvVarListEditor.tsx`:**

1. **Added `suggestions` prop** - The component now accepts an optional array of `SettingSuggestion[]`

2. **Created `SettingPathSelector` component** - A new internal component that:
   - Shows a dropdown when suggestions are available
   - Groups suggestions into "Suggested matches" (based on `findMatchingSuggestions` scoring) and "All settings"
   - Shows a checkmark (✓) next to settings that already have values configured
   - Shows a "Configured" indicator when a setting with a value is selected
   - Falls back to a text input when no suggestions are provided

3. **Wired up the data flow** - `EnvVarRow` now receives and passes `suggestions` to the `SettingPathSelector`

To use this feature in `ImportFromGitHubModal`, you'll need to fetch available settings and pass them as the `suggestions` prop. For example:

```tsx
<EnvVarListEditor
  envVars={envVars}
  onChange={setEnvVars}
  allowNameEdit={true}
  suggestions={availableSettings} // fetch from API
/>
```
★ Insight ─────────────────────────────────────
**What We Solved:**
1. **Swagger is just documentation** - OpenAPI spec is the source of truth
2. **`openapi-python-client`** generates typed Python code from that spec
3. **Auto-generated = always in sync** - regenerate when API changes
4. **Wrapper pattern** - Don't edit generated code, wrap it with custom logic
─────────────────────────────────────────────���───

I've built a **modern, auto-generated CLI system** for ushadow that solves your maintenance problem. Here's what you now have:

```
FastAPI Backend
    ↓ (auto-generates)
OpenAPI Spec (openapi.json)
    ↓ (openapi-python-client reads)
Type-Safe Python Client (clients/python/ushadow_api_client/)
    ↓ (wrapped by)
Authenticated Client (ushadow_auth_client.py)
    ↓ (used by)
Beautiful CLI Tool (scripts/ush)
```

1. **`scripts/generate_openapi_spec.py`** - Downloads OpenAPI from backend
2. **`clients/python/ushadow_api_client/`** - Auto-generated client (don't edit!)
3. **`clients/python/ushadow_auth_client.py`** - Auth wrapper (customizable)
4. **`scripts/ush`** - Modern CLI with Typer + Rich
5. **`scripts/regenerate_client.sh`** - One-command regeneration
6. **`docs/CLI-TOOL-GUIDE.md`** - Complete usage guide

```bash
./scripts/ush health

./scripts/ush services list

./scripts/ush services start chronicle-backend

./scripts/ush -v services stop chronicle

./scripts/ush api GET /api/services/
```

```bash
./scripts/regenerate_client.sh
```

```python
from clients.python.ushadow_auth_client import UshadowAuthClient

client = UshadowAuthClient.from_env()  # Auto-login
services = client.list_services()      # Full type hints!

for svc in services:
    print(f"{svc.service_name}: {svc.status}")  # IDE autocomplete!
```

| Feature | Before | After |
|---------|--------|-------|
| **Maintenance** | Manual endpoint updates | Auto-regenerate |
| **Type Safety** | None | Full Pydantic models |
| **API Coverage** | Partial | 100% automatic |
| **CLI Output** | Plain text | Rich tables & colors |
| **Testing** | Manual mocking | Type-safe models |

1. **Test it:** Start your backend and run `./scripts/ush services list`
2. **Read the guide:** See `docs/CLI-TOOL-GUIDE.md` for full documentation
3. **Add to CI/CD:** Use `regenerate_client.sh` to keep client in sync
4. **Extend it:** Add more commands to `scripts/ush` as needed

Your old `scripts/ushadow_client.py` still works! Migrate gradually:
- **Phase 1:** Use new `ush` for new features
- **Phase 2:** Update existing scripts to use `UshadowAuthClient`
- **Phase 3:** Remove old client once everything migrated

> "Is there a better way to build and maintain a CLI tool like this?"

**Yes!** Build on top of OpenAPI/Swagger:
- **FastAPI** auto-generates the OpenAPI spec
- **`openapi-python-client`** auto-generates Python code from spec
- **Your wrapper** adds authentication and convenience methods
- **Regenerate** when API changes (not manual maintenance!)

This approach works for **LLM testing, user testing, and API testing** because:
- LLMs can see full type information
- Users get beautiful CLI output
- API tests use type-safe models

See `IMPLEMENTATION_SUMMARY.md` for complete details!

* ★ Insight ─────────────────────────────────────
**Trade-off:**
- `openapi-python-client` is designed for pip packages → deep nesting
- For internal use, we could just flatten it or write a simpler custom client
- The generated code is good, but the structure is overkill for a CLI tool
─────────────────────────────────────────────────

**Here's what I propose:** Let me flatten it and create a simpler structure. The key question is:

**Do you want:**
1. **Keep auto-generated** - Move files up, import as `from ushadow.client import ...`
2. **Custom lightweight client** - Write a simpler ~100 line client that uses httpx directly, still type-safe but no generator overhead

Option 2 would look like:

```python
from .api import UshadowClient
from .models import Service, ServiceStatus
```

```
ushadow/client/
├── __init__.py      # Exports
├── api.py           # UshadowClient class (~80 lines)
├── models.py        # Pydantic models (~40 lines)
└── auth.py          # Authentication logic (~50 lines)
```

Which approach do you prefer? The custom one is simpler to maintain but requires updating when the API changes. The auto-generated one regenerates automatically but has more files.

* ★ Insight ─────────────────────────────────────
**Simplified Structure:**
- `ushadow/client/` - Everything lives here now
- `auth.py` - Your custom wrapper (won't be overwritten)
- `api/`, `models/`, `client.py` - Auto-generated (regenerate when API changes)
- Much cleaner than the nested `clients/python/ushadow_api_client/` structure
─────────────────────────────────────────────────

Done! Here's your simplified structure:

```
ushadow/client/
├── __init__.py          # Exports
├── auth.py              # Your custom wrapper (keep this)
├── client.py            # Auto-generated base client
├── errors.py            # Auto-generated
├── types.py             # Auto-generated
├── api/
│   └── default/
│       ├── list_services.py
│       ├── start_service.py
│       └── ...
└── models/
    ├── service.py
    └── ...
```

**Usage:**

```python
from ushadow.client.auth import UshadowClient

client = UshadowClient.from_env()
services = client.list_services()
```

**CLI:**

```bash
./scripts/ush services list
./scripts/ush services start chronicle
```

**Regenerate when API changes:**

```bash
./scripts/regenerate_client.sh
```

The `auth.py` file is preserved during regeneration - only the auto-generated parts get replaced.

* Done! Now the structure is:

```
Ushadow/
├── ush                          # CLI tool (run with ./ush)
├── ushadow/
│   └── client/
│       ├── __init__.py
│       ├── auth.py              # Your custom wrapper
│       ├── api/default/         # Auto-generated
│       └── models/              # Auto-generated
└── scripts/
    ├── regenerate_client.sh
    └── generate_openapi_spec.py
```

**Usage:**
```bash
./ush health
./ush services list
./ush services start chronicle
```

* Now regenerate the lockfile:

```bash
cd ushadow/backend && uv lock
```

Or if you want to keep 0.19.x, ensure gcc is available in your build environment. The Dockerfile already has it, so try:

```bash
docker compose build --no-cache backend
```

Which approach do you want to try?

* All fixed! Here's the summary:

**1. Error with `ush services list`**
- The auto-generated models didn't match the real API response
- Regenerated the client from your **actual running server** (`http://localhost:8040/openapi.json`)
- Simplified `auth.py` to use raw JSON dicts instead of complex Pydantic models

**2. Help for missing subcommands**
- Added `no_args_is_help=True` to both the main app and services subcommand
- Now `./ush` and `./ush services` show their help menus

**3. Missing functionality**
Added commands that were in your old client:
- `restart` - Restart a service
- `logs` - View service logs
- `env-export` - Export environment variables

```
ushadow/client/
├── auth.py         # Your wrapper (uses httpx directly for simplicity)
├── client.py       # Auto-generated base client
├── api/            # Auto-generated endpoints (153 total!)
└── models/         # Auto-generated models (218 types)
```

★ Insight ─────────────────────────────────────
**Lesson Learned:**
The auto-generated Pydantic models are fragile when the API response doesn't exactly match. For a CLI tool, using raw `dict` responses is more robust. The generated client is still useful for type hints and IDE autocomplete in other contexts.
─────────────────────────────────────────────────

```bash
./ush                          # Shows help
./ush services                 # Shows services help
./ush services list            # Lists services with pretty table
./ush services restart mem0    # Restart a service
./ush services logs mem0 -n 50 # View logs
```

* All tasks are complete. Here's a summary of what was accomplished:

★ Insight ─────────────────────────────────────
**Final Architecture:**
- `./ush` - Dynamic CLI that parses `openapi.json` at runtime
- `ushadow/client/auth.py` - Simple HTTP wrapper (~200 lines)
- `openapi.json` - Source of truth, refreshed from running backend
- `scripts/regenerate_client.sh` - Just downloads fresh spec

**Key Win:** Adding new API endpoints requires zero CLI code changes - just refresh the spec!
─────────────────────────────────────────────────

**Summary of completed work:**

1. **Deleted generated SDK code** - Removed the 50k+ lines of auto-generated models that weren't being used

2. **Simplified client** - `ushadow/client/auth.py` is now a clean ~200 line HTTP wrapper with authentication

3. **Rewrote ush CLI** - Dynamic command discovery from `openapi.json`:
   - Auto-discovers all 16 command groups (127+ commands total)
   - Parses path/query parameters automatically
   - Shows contextual help for each group
   - Handles authentication automatically

4. **Added formatters** - Pretty table output for `services list` and `providers list`

5. **Simplified regenerate script** - Now just downloads fresh `openapi.json` (went from 86 lines to 48 lines)

**How updates work now:**
```bash
./scripts/regenerate_client.sh

./ush  # Shows all command groups with updated counts
```

* Done! Here's what was implemented:

★ Insight ─────────────────────────────────────
**Interactive Shell Features:**
- **Nested completion**: Tab once for groups, Tab again for commands
- **Fish-style suggestions**: Grey text from history (press → to accept)
- **Persistent history**: Saved to `~/.ush_history` across sessions
- **Parameter hints**: Shows `<name> required` for path params, `--flag optional` for query params
─────────────────────────────────────────────────

**Usage:**

```bash
./ush shell
```

**What you'll see:**

```
ushadow shell - Type commands, Tab to complete, ↑/↓ for history
Type 'help' for commands, 'exit' to quit

ushadow> ser<TAB>           # Completes to "services"
ushadow> services <TAB>     # Shows: list, start, stop, restart...
ushadow> services start <TAB>  # Shows: <name> required
ushadow> services logs mem0 --<TAB>  # Shows: --tail optional
```

**Key bindings:**
- **Tab** - Complete command/parameter
- **→** (right arrow) - Accept fish-style suggestion from history
- **↑/↓** - Navigate command history
- **Ctrl+C** - Cancel current line
- **exit** or **Ctrl+D** - Quit shell

* Fixed. I added `rich>=13.0.0` to `pyproject.toml` so it will be installed automatically when the backend environment is set up. The CLI should now work - try `./ush shell` to enter interactive mode.

* You can install the dependencies a few ways:

**Option 1: Using the backend's uv environment** (recommended if you're running the backend)
```bash
cd ushadow/backend
uv sync
```

**Option 2: Direct pip install** (quick for just the CLI)
```bash
pip install rich prompt_toolkit httpx
```

**Option 3: From the project root with make** (if you have a Makefile)
```bash
make install
make up  # usually installs deps and starts services
```

After installing, test with:
```bash
./ush shell
```

Which option works best for your setup?

* ★ Insight ─────────────────────────────────────
**Changes made:**
1. `reserve_space_for_menu=8` - Reserves 8 lines above prompt so menu is visible at bottom of screen
2. Fixed `at_new_word` logic - Tab on empty line or after space now shows ALL options
3. Added completion menu styling - Green highlight for current selection
─────────────────────────────────────────────────

Try it now:

```bash
./ush shell
```

Then:
- Press **Tab** immediately → shows all command groups
- Type `services ` (with space) then **Tab** → shows all services commands
- Use **↑/↓** arrows to navigate the menu
- Press **Enter** to select

* ★ Insight ─────────────────────────────────────
**Resource-first completion now works:**
- `services <Tab>` → shows `list`, `catalog` (commands) AND `mem0`, `chronicle` (resources)
- `services mem0 <Tab>` → shows `start`, `stop`, `status`, `logs` (commands for that resource)
- `services mem0 start` executes the same as `services start mem0`

**The mapping in `get_resource_endpoints()`** defines which groups support this:
```python
"services": ("/api/services/", "service_name"),
"providers": ("/api/providers/", "id"),
"instances": ("/api/instances/", "name"),
```
─────────────────────────────────────────────────

Done! Try it out:

```bash
./ush shell
```

Then:
- `services <Tab>` → shows services AND commands like `list`
- Select `mem0` → `services mem0 <Tab>` → shows commands like `start`, `stop`, `status`
- `services mem0 start` → starts the service

Both orderings work:
- `services mem0 start` (resource-first)
- `services start mem0` (command-first)

To add more groups with resource completion, edit `get_resource_endpoints()` around line 333 in `./ush`.

* ★ Insight ─────────────────────────────────────
**How updates work - two separate concerns:**

1. **OpenAPI spec (`openapi.json`)** - Source of truth for commands
   - Refresh with: `./scripts/regenerate_client.sh`
   - Or manually: `curl http://localhost:8000/openapi.json > openapi.json`
   - New API endpoints appear automatically in CLI

2. **Resource endpoints (`get_resource_endpoints()`)** - Manual mapping
   - This tells the completer *how* to fetch resource names for Tab completion
   - Must be updated manually when you add new resource groups
   - Located at line ~344 in `./ush`
─────────────────────────────────────────────────

**Keeping it up to date:**

| What | How | When |
|------|-----|------|
| **Commands** | `./scripts/regenerate_client.sh` | After adding/changing API endpoints |
| **Resource completion** | Edit `get_resource_endpoints()` in `./ush` | When adding new resource types |

The **commands auto-discover** from OpenAPI - that's the dynamic part. The **resource mapping is manual** because we need to know which endpoint returns the list and which field contains the name (e.g., `service_name` vs `id` vs `name`).

**Potential improvement**: We could auto-detect list endpoints by looking for `GET /api/{group}/` patterns in the OpenAPI spec and guessing common name fields (`name`, `id`, `{group}_name`). Want me to add that?

* Done! Here's the summary:

★ Insight ─────────────────────────────────────
**How updates work now:**

| What changes | How to update | Automatic? |
|--------------|---------------|------------|
| **New API endpoints** | `./scripts/regenerate_client.sh` | ✅ Yes - commands auto-discover |
| **New resource groups** | Auto-detected from OpenAPI | ✅ Yes - if follows `/api/{group}/` pattern |
| **Weird name fields** | Add to `overrides` in `get_resource_endpoints()` | ❌ Manual |

**The flow:**
1. OpenAPI spec defines endpoints → commands auto-discovered
2. List endpoints (`GET /api/{group}/`) → auto-detected for completion
3. Name field → guessed from response using `guess_name_field()`
4. Edge cases → manual override in `get_resource_endpoints()`
────────────────────────────────────────────────��

**To refresh after API changes:**
```bash
./scripts/regenerate_client.sh
```

That's it - new endpoints, new resource groups, and their completions will appear automatically. Only add manual overrides if the name field guessing fails for a specific group.
…cement, and UI contract (#113)

* I've created a comprehensive plan at `docs/FRONTEND-EXCELLENCE-PLAN.md`. Here's the summary:

`★ Insight ─────────────────────────────────────`
**Key insight for agent-friendly codebases**: Agents need *scannable, compact references*—not verbose documentation. A 1500-token quick reference file that agents can read in one context window is far more valuable than 20K tokens of scattered docs.
`─────────────────────────────────────────────────`

Agents re-implement features because they can't easily discover existing patterns. The solution is **discoverability + enforcement**.

| Phase | What | Impact |
|-------|------|--------|
| **1. Component Registry** | Single MD file listing all reusable components with examples | Agents find components before coding |
| **2. ESLint Rules** | `max-lines` (300/page), `boundaries`, import order | Forces extraction, prevents sprawl |
| **3. Hook Patterns** | Add `useModal`, `useClipboard`, `useDebounce` + pattern docs | Stop common re-implementations |
| **4. Agent Quick Ref** | ~1500 token cheat sheet with copy-paste examples | Fast context loading |
| **5. Pre-Commit** | Husky + lint-staged + testid checker | Catch issues before commit |
| **6. Directory Conventions** | Enforce `index.ts` exports, flat structure | Clean imports |
| **7. CLAUDE.md Updates** | "Search before creating" workflow | Behavioral change |

These 4 changes take ~2 hours and have immediate impact:
1. Create `AGENT_QUICK_REF.md`
2. Create `COMPONENT_REGISTRY.md`
3. Add `max-lines` ESLint rule
4. Update CLAUDE.md with search-first workflow

Your codebase already has:
- Well-documented reusable components (`Modal`, `SecretInput`, `SettingField`)
- Central hooks export (`hooks/index.ts`)
- Consistent `data-testid` patterns
- React Query + react-hook-form patterns

The gap is **agent discoverability**—the patterns exist but agents don't find them.

---

Would you like me to implement any of the quick wins now? I'd suggest starting with:
1. **`COMPONENT_REGISTRY.md`** - Catalog your existing components
2. **`AGENT_QUICK_REF.md`** - Create the compact reference

* I've created the contract file at `frontend/src/testing/ui-contract.ts`. There are two decisions for you to make:

---

In the `componentRegistry` object, you need to define entries for each reusable component. Here's the structure:

```typescript
export const componentRegistry: Record<string, ComponentInfo> = {
  SecretInput: {
    import: '@/components/settings/SecretInput',
    description: 'API key/password input with visibility toggle',
    testIdPattern: 'secretInput',
    requiredProps: ['id', 'name', 'value', 'onChange'],
  },
  // Add more components...
}
```

**Your call**: Which components should be in the registry? I'd suggest starting with the ones agents most commonly re-implement:
- `SecretInput`, `SettingField`, `Modal`, `ConfirmDialog`, `SettingsSection`

---

This is the interesting one. In `ui-contract.ts:124`, implement the function to generate usage examples.

**The trade-off to consider**:
- **Minimal examples** (~5 lines): Lower token cost, but agents might miss the react-hook-form integration pattern
- **Full examples** (~15 lines): Higher token cost, but agents get copy-paste-ready code
- **Contextual**: Pass a `mode` param to get either minimal or full

Here's a starting point - the function signature is already in place. You could implement it like:

```typescript
export function getComponentUsageExample(
  componentName: string,
  id: string
): string {
  const examples: Record<string, string> = {
    SecretInput: `<SecretInput id="${id}" name="${id}" value={value} onChange={setValue} />`,
    // ... or the full Controller pattern?
  }
  return examples[componentName] ?? `// No example for ${componentName}`
}
```

**Question for you**: Should examples default to standalone usage or react-hook-form integrated usage? Your codebase uses react-hook-form extensively, so the full pattern might be more useful for agents—but it's also 3x the tokens.

* Done. Here's what we now have:

**Created**: `frontend/src/testing/ui-contract.ts`

This is now your **single source of truth** that combines:
1. **Component documentation** - Import paths, examples, when to use
2. **TestID patterns** - Functions that generate the exact testid strings
3. **POM contract** - Imported by `BasePage.ts` for Playwright locators

```
┌─────────────────────────────────────────────────────────────────┐
│                     ui-contract.ts                              │
│  - JSDoc examples (for agents)                                  │
│  - TestID pattern functions (for React + Playwright)            │
└─────────────────────────────────────────────────────────────────┘
         │                                    │
         ▼                                    ▼
┌─────────────────────┐            ┌─────────────────────┐
│  React Components   │            │  Playwright POM     │
│  import { modal }   │            │  import { modal }   │
│  data-testid={      │            │  getByTestId(       │
│    modal.close(id)  │            │    modal.close(id)  │
│  }                  │            │  )                  │
└─────────────────────┘            └───────────��─────────┘
```

**Benefits**:
- Change a pattern in one place → TypeScript catches all breakages
- Agents read one file to understand component + testing contract
- No more "what's the testid format again?" - just call the function

**Next step**: The React components should also import from `ui-contract.ts` instead of hardcoding strings. Want me to update `SecretInput.tsx` as an example of how components would use the contract?

* ## Summary

Here's what we've built to address your original goals:

| Problem | Solution |
|---------|----------|
| Agents re-implement instead of reuse | `AGENT_QUICK_REF.md` + CLAUDE.md workflow mandates searching first |
| No separation of presentation/logic | `HOOK_PATTERNS.md` documents 5 patterns for extracting logic to hooks |
| Inconsistent styles | ESLint `max-lines` rule forces small files, `ui-contract.ts` standardizes patterns |
| Messy, sprawling code | File size limits (300/150/80 lines) enforced by ESLint |

```
ushadow/
├── CLAUDE.md                              # Added search-before-creating workflow
└── ushadow/frontend/
    ├── AGENT_QUICK_REF.md                 # NEW: ~800 token component reference
    ├── eslint.config.js                   # NEW: max-lines rules
    └── src/
        ├── testing/
        │   └── ui-contract.ts             # NEW: Shared testid patterns + docs
        ├── hooks/
        │   └── HOOK_PATTERNS.md           # NEW: 5 patterns for logic separation
        └── components/settings/
            └── SecretInput.tsx            # Updated to use ui-contract
```

1. **Agent starts frontend task** → Reads `AGENT_QUICK_REF.md` (mandated by CLAUDE.md)
2. **Searches for existing components** → grep commands in CLAUDE.md
3. **Finds component in ui-contract.ts** → Gets import path + example + testid pattern
4. **Writes code** → ESLint warns if file exceeds 300 lines
5. **Extracts logic** → Follows `HOOK_PATTERNS.md` patterns
6. **POM tests work** → Same testid patterns used everywhere

The key insight: **discoverability + enforcement**. Agents now have a clear path to find existing code, and ESLint prevents the sprawl even if they don't.

* Done. Updated to 600 lines.

**What's next?** Looking at the original plan, here are the remaining high-value items:

| Item | Impact | Effort |
|------|--------|--------|
| **Add `useModal` hook** | High - agents keep re-implementing modal state | 10 min |
| **Add `useClipboard` hook** | Medium - common pattern | 5 min |
| **Migrate more components to ui-contract** | Medium - consistency | 30 min per component |
| **Pre-commit hook** (Husky + lint-staged) | Medium - catches issues early | 15 min |

My recommendation: **Add the missing utility hooks** (`useModal`, `useClipboard`, `useDebounce`). These are small, frequently needed, and agents will re-implement them constantly if they don't exist.

Want me to add those hooks, or would you prefer to tackle something else first?

* `★ Insight ─────────────────────────────────────`
The Modal uses `z-[9999]` which technically works but:
1. It's inconsistent with your defined z-index scale (`z-modal` = 60)
2. Makes it hard to reason about stacking order
3. If someone creates a nested modal or toast, they might use `z-[10000]` starting an arms race

However, since `z-modal` is only 60, and modals really do need to be on top of everything, we have two choices:
- Change Modal to use `z-modal` and increase `z-modal` value in tailwind config
- Keep the high value but use a semantic name
`─────────────────────────────────────────────────`

**Question for you**: The current z-index scale in tailwind.config.js is:
- `z-sticky`: 40
- `z-dropdown`: 50
- `z-modal`: 60
- `z-toast`: 70

But the Modal uses `z-[9999]`. Should I:
1. **Keep 9999** but document it as the "nuclear option" for portaled modals
2. **Update the scale** to something like modal=1000, toast=1100 (more breathing room)
3. **Leave as-is** since it's working and portaled to body anyway

The risk of changing is breaking something that currently works. The risk of not changing is agents might use inconsistent values.

* Good. Now let's take stock of what we've built and what's next.

| File | Purpose |
|------|---------|
| `CLAUDE.md` | Workflow: search → check contract → follow patterns |
| `frontend/AGENT_QUICK_REF.md` | ~200 line reference with components, hooks, **common bugs** |
| `frontend/eslint.config.js` | Max 600 lines, complexity limits |
| `frontend/src/testing/ui-contract.ts` | TestID patterns + component docs |
| `frontend/src/hooks/HOOK_PATTERNS.md` | 5 patterns for separating logic |
| `frontend/e2e/pom/BasePage.ts` | Updated to use ui-contract |
| `SecretInput.tsx` | Example of using ui-contract |

The foundation is solid. Here are options ranked by impact:

1. **Test it** - Have an agent build something and see if they follow the workflow
2. **Add more to ui-contract** - ServiceCard, EnvVarEditor (you mentioned these)
3. **Create a Dropdown component** - To prevent the cutoff bug you mentioned
4. **Add design review plugin integration** - You mentioned having design plugins

Which direction interests you? Or is there a specific bug/pattern you'd like to address first?
- Exclude .venv/ to prevent Mac virtual env from overwriting Linux build
- Remove overly broad exclusions (*.md, docs/, .github/)
- Keep essential excludes: Python cache, testing artifacts, IDE files

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant